<?php


namespace App\Services;


use App\Models\User;
use Illuminate\Support\Facades\Cache;

class WxTokenService extends BaseTokenService
{
    /**
     * 调用微信登录接口 wx.login 返回的凭证进而换取用户登录态信息，
     * 包括用户在当前小程序的唯一标识（openid）、微信开放平台账号下的唯一标识（unionid，若当前小程序已绑定到微信开放平台账号）及本次登录的会话密钥（session_key）等。
     * 用户数据的加解密通讯需要依赖会话密钥完成。
     * @var string
     */
    protected string $code;
    protected string $wxAppId;
    protected string $wxAppSecret;
    protected string $wxLoginUrl;

    /**
     * 拼接完整的微信请求 URL
     * WxTokenService constructor.
     * @param $code
     */
    public function __construct($code)
    {
        $this->code        = $code;
        $this->wxAppId     = env('WX_APP_ID');
        $this->wxAppSecret = env('WX_APP_SECRET');
        $this->wxLoginUrl  = sprintf(env('WX_LOGIN_URL'), $this->wxAppId, $this->wxAppSecret, $this->code);
    }

    /**
     * 获取 Token
     * @return string
     * @throws \Exception
     */
    public function getToken()
    {
        //$result   = curl_get($this->wxLoginUrl);   // 请求微信登录api，返回的是 JSON 格式的数据
        //$wxLoginResult = json_decode($result, true);
        $wxLoginResult = [
            "session_key" => "/jH3UNc0bEDpXwsEyi8qgw==",
            "openid"      => "o7hF160HS4CWTV0UqW139-o5IZd4"
        ];
        if (empty($wxLoginResult)) { // code输入错误时，会没有结果返回
            throw new \Exception('获取 session_key 或者 openid 时异常，微信内部错误');
        } else {
            // 这个 errcode 是微信登录 api 返回的结果
            $loginFail = array_key_exists('errcode', $wxLoginResult);
            if ($loginFail) {
                // 抛出微信异常
                throw new \Exception(json_encode($wxLoginResult));
                // $this->processLoginError($wxLoginResult);
            } else {
                return $this->grantToken($wxLoginResult); // 生成 token
            }
        }
    }

    /* *
     * 获取Token令牌
     * @param $wxLoginResult
     * @return mixed
     */
    private function grantToken($wxLoginResult)
    {
        /**
         * 1. 拿到 openid；
         * 2. 去数据库看一下这个 openid 是否存在；如果存在就不做处理，不存在就插入
         * 3. 生成令牌，准备缓存数据，写入缓存；
         * 4. 把令牌返回到客户端去
         * key: 令牌(就是Token)
         * value: wxResult, uid(openid太长了,可以用这个来替代)
         */
        $openid = $wxLoginResult['openid'];
        $user   = User::getByOpenID($openid);
        if ($user) {
            $uid = $user->id;   // 如果有该用户就取出用户id
        } else {
            $uid = $this->newUser($openid); // 没有就代表新用户
        }
        //准备缓存数据
        $cacheValue = $this->prepareCacheValue($wxLoginResult, $uid);
        return $this->saveToCache($cacheValue);
    }

    /**
     * 第二步: 生成新用户
     * @param $openid
     * @return integer
     */
    private function newUser($openid): int
    {
        $user = User::query()->create(['openid' => $openid]);
        return $user->id;
    }

    /**
     * 第三步: 准备缓存数据  目的: 通过令牌读取用户的相关信息和加快访问速度
     * @param $wxLoginResult
     * @param $uid
     * @return mixed
     */
    private function prepareCacheValue($wxLoginResult, $uid): mixed
    {
        $cacheValue        = $wxLoginResult;
        $cacheValue['uid'] = $uid;
        return $cacheValue;
    }

    /**
     * 第四步： 存入缓存，返回Token
     * @param $cacheValue
     * @return string
     * @throws \Exception
     */
    private function saveToCache($cacheValue): string
    {
        $key       = self::generateToken();       //生成Token(生成令牌)
        $value     = json_encode($cacheValue);
        $expire_in = env('WX_TOKEN_EXPIRE_IN');
        $cacheRet  = Cache::put($key, $value, $expire_in);
        if (!$cacheRet) {
            throw new \Exception('服务器缓存异常');
        }
        return $key;  // key 就是令牌
    }
}
